import { cloneDeep } from 'lodash-es';
import { MODEL_NAME } from '../../constants/model-names';
import {
  ITEM_TYPE,
  type EquipmentItemConfig,
  type EquipSlot,
} from '../../data/item/type';
import { EVENTS, type IEventBus } from '../../events';
import type {
  EquipmentPanelModel,
  InventoryModel,
  PlayerAttributesModel,
} from '../../model';
import type { InventorySlotItem } from '../../model/inventory/type';
import { BaseSystem } from '../base';
import { ItemReader } from '../item/reader';
import type { PlayerStateSystem } from '../player-state';
import { ATTRIBUTE_SOURCE } from '../../model/attributes/character-attributes';
import type { Attribute, AttributeType } from '../../model/attributes/type';
import type { EquipmentPanelUISnapshot } from './type';

export class EquipmentPanelSystem extends BaseSystem {
  private bus: IEventBus;
  private playerStateSystem: PlayerStateSystem;

  constructor(bus: IEventBus, playerStateSystem: PlayerStateSystem) {
    super();
    this.bus = bus;
    this.playerStateSystem = playerStateSystem;

    this.bus.on(EVENTS.EQUIPMENT_PANEL_EQUIP, (inventorySlotItem) => {
      this.equip(inventorySlotItem);
    });

    this.bus.on(EVENTS.EQUIPMENT_PANEL_UNEQUIP, (inventorySlotItem) => {
      this.unequip(inventorySlotItem);
    });
  }

  private equip(inventorySlotItem: InventorySlotItem) {
    if (!inventorySlotItem.payload?.equip) return;
    const equipmentPanelModel =
      this.playerStateSystem.getModel<EquipmentPanelModel>(
        MODEL_NAME.EQUIPMENT_PANEL
      );
    const inventoryModel = this.playerStateSystem.getModel<InventoryModel>(
      MODEL_NAME.INVENTORY
    );
    const playerAttributesModel =
      this.playerStateSystem.getModel<PlayerAttributesModel>(
        MODEL_NAME.PLAYER_ATTRIBUTES
      );
    if (!equipmentPanelModel || !inventoryModel || !playerAttributesModel)
      return;
    const itemId = inventorySlotItem.itemId;
    const itemConfig = ItemReader.getConfig(itemId);
    if (!itemConfig || itemConfig.type !== ITEM_TYPE.EQUIPMENT) return;
    const equipSlot = itemConfig.equipSlot;
    const oldSlotItem = equipmentPanelModel.getSlot(equipSlot);
    if (oldSlotItem) {
      inventoryModel.addEquip(oldSlotItem);
    }
    const newInventorySlotItem = cloneDeep(inventorySlotItem);
    inventoryModel.removeEquip(inventorySlotItem);
    equipmentPanelModel.equip(equipSlot, newInventorySlotItem);

    playerAttributesModel.removeModifiersBySource(ATTRIBUTE_SOURCE.EQUIPMENT);

    const slots = Object.values(equipmentPanelModel.getSlots()).filter(
      (v) => v !== null
    );

    slots.forEach((equipItem) => {
      const attributes = equipItem.payload?.equip?.baseAttrs.reduce(
        (acc, attr) => {
          acc[attr.prop as AttributeType] = attr.value;
          return acc;
        },
        {} as Partial<Attribute>
      );
      if (attributes) {
        playerAttributesModel.addModifier({
          source: ATTRIBUTE_SOURCE.EQUIPMENT,
          key: equipItem.itemId,
          attributes,
        });
      }
    });
    this.bus.emit(EVENTS.EQUIPMENT_PANEL_EQUIP_COMPLETED);
  }

  private unequip(inventorySlotItem: InventorySlotItem) {
    if (!inventorySlotItem.payload?.equip) return;
    const equipmentPanelModel =
      this.playerStateSystem.getModel<EquipmentPanelModel>(
        MODEL_NAME.EQUIPMENT_PANEL
      );
    const inventoryModel = this.playerStateSystem.getModel<InventoryModel>(
      MODEL_NAME.INVENTORY
    );
    const playerAttributesModel =
      this.playerStateSystem.getModel<PlayerAttributesModel>(
        MODEL_NAME.PLAYER_ATTRIBUTES
      );
    if (!equipmentPanelModel || !inventoryModel || !playerAttributesModel)
      return;
    const itemId = inventorySlotItem.itemId;
    const itemConfig = ItemReader.getConfig(itemId);
    if (!itemConfig || itemConfig.type !== ITEM_TYPE.EQUIPMENT) return;
    const equipSlot = itemConfig.equipSlot;
    playerAttributesModel.removeModifier(ATTRIBUTE_SOURCE.EQUIPMENT, itemId);
    inventoryModel.addEquip(inventorySlotItem);
    equipmentPanelModel.unequip(equipSlot);
    this.bus.emit(EVENTS.EQUIPMENT_PANEL_UNEQUIP_COMPLETED);
  }

  public getUISnapshot() {
    const equipmentPanelModel =
      this.playerStateSystem.getModel<EquipmentPanelModel>(
        MODEL_NAME.EQUIPMENT_PANEL
      );
    if (!equipmentPanelModel)
      return {
        slots: {},
        slotsConfig: {},
      } as EquipmentPanelUISnapshot;
    const slots = equipmentPanelModel.getSlots();
    const slotsConfig = Object.keys(slots).reduce((acc, k) => {
      const key = k as EquipSlot;
      const slot = slots[key];
      if (!slot) {
        acc[key] = null;
        return acc;
      }
      const slotConfig = ItemReader.getConfig(slot?.itemId);
      if (!slotConfig || slotConfig.type !== ITEM_TYPE.EQUIPMENT) {
        acc[key] = null;
        return acc;
      }
      acc[key] = slotConfig;
      return acc;
    }, {} as Record<EquipSlot, EquipmentItemConfig | null>);

    return {
      slots,
      slotsConfig,
    };
  }

  /** 初始化穿戴装备对属性的加成 */
  private initAttributes() {
    const equipmentPanelModel =
      this.playerStateSystem.getModel<EquipmentPanelModel>(
        MODEL_NAME.EQUIPMENT_PANEL
      );
    const playerAttributesModel =
      this.playerStateSystem.getModel<PlayerAttributesModel>(
        MODEL_NAME.PLAYER_ATTRIBUTES
      );
    if (!equipmentPanelModel || !playerAttributesModel) return;
    const slots = equipmentPanelModel.getSlots();
    const slitList = Object.values(slots).filter((v) => v !== null);
    slitList.forEach((equipItem) => {
      const attributes = equipItem.payload?.equip?.baseAttrs.reduce(
        (acc, attr) => {
          acc[attr.prop as AttributeType] = attr.value;
          return acc;
        },
        {} as Partial<Attribute>
      );
      if (attributes) {
        playerAttributesModel.addModifier({
          source: ATTRIBUTE_SOURCE.EQUIPMENT,
          key: equipItem.itemId,
          attributes,
        });
      }
    });
  }

  public override initAfter(): void {
    this.initAttributes();
  }
}
